#include "util/dbbstring.h"

#include <cctype>
#include <cstdarg>
#include <cstring>
#include <cstdio>
#include <cstdlib>

// very small strings will likely cause reallocations
static const int MININUM_STRING_CAPACITY = 65 - sizeof(String::Rep) - 1;

//------------------------------------------------------------------------
char
String::ToUpper(char c)
{
	return (c>='a'&&c<='z')
			? (char)(c - ('a' - 'A'))
			: c;
}

//------------------------------------------------------------------------
char
String::ToLower(char c)
{
	return (c>='A'&&c<='Z')
			? (char)(c - ('a' - 'A'))
			: c;
}

//------------------------------------------------------------------------
dbbsize
String::Vsnprintf(char* buf, dbbsize len, const char* fmt, void* args)
{
	va_list vargs;
	vargs = reinterpret_cast<va_list>(args);
	dbbsize ret = _vsnprintf(buf,len,fmt,vargs);
	dbbassert(ret<=len);
	return ret;
}

//------------------------------------------------------------------------
dbbsize
String::Snprintf(char* buf, dbbsize n, const char* fmt, ...)
{
	va_list vlist;
	va_start(vlist,fmt);
	dbbsize ret = _vsnprintf(buf,n,fmt,vlist);
	dbbassert(ret<=n);
	va_end(vlist);
	return ret;
}

//------------------------------------------------------------------------
dbbsize
String::Sprintf(char* buf, const char* fmt, ...)
{
	va_list vlist;
	va_start(vlist,fmt);
	dbbsize ret = ::vsprintf(buf,fmt,vlist);
	va_end(vlist);
	return ret;
}

//------------------------------------------------------------------------
int
String::StrCmp(const char* lhs, const char* rhs)
{
	dbbassert(lhs);
	dbbassert(rhs);
	return strcmp(lhs,rhs);
}

//------------------------------------------------------------------------
int
String::StrNcmp(const char* lhs, const char* rhs, size_t n)
{
	dbbassert(lhs);
	dbbassert(rhs);
	return strncmp(lhs,rhs,n);
}

//------------------------------------------------------------------------
int
String::StrCasecmp(const char* lhs, const char* rhs)
{
	dbbassert(lhs);
	dbbassert(rhs);
	dbbindex i=0;
	while(lhs[i]||rhs[i])
	{
		if(ToLower(lhs[i])<ToLower(rhs[i]))
		{
			return -1;
		}
		else if(ToLower(lhs[i])>ToLower(rhs[i]))
		{
			return 1;
		}
		++i;
	}
	return 0;
}

//------------------------------------------------------------------------
int
String::StrNcasecmp(const char *lhs, const char *rhs, dbbsize n)
{
	dbbassert(lhs);
	dbbassert(rhs);
	dbbindex i=0;
	while(lhs[i]||rhs[i]&&i<n)
	{
		if(ToLower(lhs[i])<ToLower(rhs[i]))
		{
			return -1;
		}
		else if(ToLower(lhs[i])>ToLower(rhs[i]))
		{
			return 1;
		}
		++i;
	}
	return 0;
}

//------------------------------------------------------------------------
void
String::StrCpy(char* dst, const char* src)
{
	dbbassert(src);
	dbbassert(dst);
	strcpy(dst,src);
}

//------------------------------------------------------------------------
void
String::StrNcpy(char *dst, const char *src, dbbsize n)
{
	dbbassert(dst||0==n);
	dbbassert(src||0==n);
	strncpy(dst,src,n);
}

//------------------------------------------------------------------------
dbbsize
String::StrLen(const char* src)
{
	dbbassert(src);
	return strlen(src);
}

//------------------------------------------------------------------------
int
String::Atoi(const char* in)
{
	return atoi(in);
}

//------------------------------------------------------------------------
float
String::Atof(const char* in)
{
	return static_cast<float>(atof(in));
}

//------------------------------------------------------------------------
const char*
String::StrStr(const char* haystack, const char* needle)
{
	return strstr(haystack,needle);
}

//------------------------------------------------------------------------
const char*
String::StrChr(const char* haystack, char needle)
{
	return strchr(haystack,needle);
}

//------------------------------------------------------------------------
const char*
String::StrRchr(const char* haystack, char needle)
{
	return strrchr(haystack,needle);
}

//------------------------------------------------------------------------
char*
String::StrDup(const char* src)
{
	dbbassert(NULL!=src);
	char* r = static_cast<char*>(_aligned_malloc((StrLen(src)+1)*sizeof(char),16));
	StrCpy(r,src);
	return r;
}

//------------------------------------------------------------------------
char*
String::StrNdup(const char* src, dbbsize maxLen)
{
	dbbassert(NULL!=src);
	dbbsize len = StrLen(src);
	if(len>maxLen)
	{
		len = maxLen;
	}
	char* r = static_cast<char*>(_aligned_malloc((StrLen(src)+1)*sizeof(char),16));
	StrNcpy(r,src,len);
	r[len] = 0;
	return r;
}

//------------------------------------------------------------------------
char*
String::StrLwr(char *src)
{
	dbbassert(NULL!=src);
	int i=0;
	while(src[i])
	{
		src[i] = (char)(ToLower(src[i]));
		++i;
	}
	return src;
}

//------------------------------------------------------------------------
char*
String::StrUpr(char* src)
{
	dbbassert(NULL!=src);
	int i=0;
	while(src[i])
	{
		src[i] = (char)(ToUpper(src[i]));
		++i;
	}
	return src;
}

//------------------------------------------------------------------------
void
String::MemCpy(void *dst, const void *src, dbbsize n)
{
	memcpy(dst,src,n);
}

//------------------------------------------------------------------------
void
String::MemMove(void* dst, const void* src, dbbsize n)
{
	memmove(dst,src,n);
}

//------------------------------------------------------------------------
void
String::MemSet(void *dst, const int c, dbbsize n)
{
	memset(dst,c,n);
}

//------------------------------------------------------------------------
int
String::MemCmp(const void* buf1, const void* buf2, dbbsize n)
{
	return memcmp(buf1,buf2,n);
}

//------------------------------------------------------------------------
void
String::Rep::freeMemory()
{
	char* charp = reinterpret_cast<char*>(this);
	_aligned_free(charp);
}

//------------------------------------------------------------------------
String::Rep*
String::Rep::clone() const
{
	Rep* r = reinterpret_cast<Rep*>(_aligned_malloc(getSize()*sizeof(char),16));
	r->m_length		= m_length;
	r->m_capacity	= m_capacity;
	r->m_references	= 0;
	MemCpy(r->getData(),getData(),m_length+1);
	return r;
}

//------------------------------------------------------------------------
String::Rep*
String::Rep::Create(dbbsize slen)
{
	dbbsize scapacity = slen;
	if(scapacity < MININUM_STRING_CAPACITY)
	{
		scapacity = MININUM_STRING_CAPACITY;
	}
	Rep* r = reinterpret_cast<Rep*>(_aligned_malloc(sizeof(Rep)+scapacity+1,16));
	r->m_length		= slen;
	r->m_capacity	= scapacity;
	r->m_references	= 0;
	return r;
}

//------------------------------------------------------------------------
char String::s_emptyStringRep[sizeof(dbbsize)+sizeof(dbbsize)+sizeof(dbbsize)+sizeof(char)];

//------------------------------------------------------------------------
void
String::printf(const char* format, ...)
{
	if(_getRep().m_references>0||_getRep().getSize()<MININUM_STRING_CAPACITY)
	{
		_getRep().unRef();
		m_string = Rep::Create(255)->getData();
	}
	va_list args;
	va_start(args, format);
	while(1)
	{
		dbbsize size	= _getRep().getSize();
		dbbsize nchars	= Vsnprintf(m_string,size/2,format,args);
		if(nchars>=0&&nchars<size)
		{
			_getRep().m_length = nchars;
			break;
		}
		else if(nchars<0)
		{
			_getRep().unRef();
			m_string	= Rep::Create(size*2>255?size*2:255)->getData();
		}
		else
		{
			_getRep().unRef();
			m_string	= Rep::Create(nchars)->getData();
		}
	}
	va_end(args);
}

//------------------------------------------------------------------------
dbbindex
String::indexOf(char c, dbbindex startIndex, dbbindex endIndex) const
{
	for(dbbindex i=startIndex;i<getLength()&&i<endIndex;i++)
	{
		if(m_string[i]==c)
		{
			return i;
		}
	}
	return -1;
}

//------------------------------------------------------------------------
dbbsize
String::lastIndexOf(char c, dbbindex startIndex, dbbindex endIndex) const
{
	if(endIndex>getLength())
	{
		endIndex = getLength();
	}
	for(dbbindex i=endIndex-1;i>=startIndex;--i)
	{
		if(m_string[i]==c)
		{
			return i;
		}
	}
	return InvalidIndex;
}

//------------------------------------------------------------------------
void
String::operator+=(const String& other)
{
	dbbsize myLength	= getLength();
	dbbsize otherLength	= other.getLength();
	dbbsize totalLength = myLength + otherLength;
	if(totalLength>_getRep().m_capacity||_getRep().m_references>0)
	{
		dbbsize longer	= (totalLength>myLength?totalLength:myLength);
		Rep* r			= Rep::Create(2*longer);
		char* p			= r->getData();
		MemCpy(p,m_string,myLength);
		_getRep().unRef();
		m_string		= r->getData();
	}
	MemCpy(m_string+myLength,other.m_string,otherLength+1);
	_getRep().m_length	= totalLength;
}

//------------------------------------------------------------------------
void
String::operator+=(const char* other)
{
	dbbsize myLength	= getLength();
	dbbsize otherLength	= StrLen(other);
	dbbsize totalLength	= myLength+otherLength;
	if(totalLength>_getRep().m_capacity||_getRep().m_references>0)
	{
		dbbsize longer	= (totalLength>myLength?totalLength:myLength);
		Rep* r			= Rep::Create(2*longer);
		char* p			= r->getData();
		MemCpy(p,m_string,myLength);
		_getRep().unRef();
		m_string		= r->getData();
	}
	MemCpy(m_string+myLength,other,otherLength+1);
	_getRep().m_length	= totalLength;
}

//------------------------------------------------------------------------
String
operator+(const String& lhs, const String& rhs)
{
	int myLength	= lhs.getLength();
	int otherLength	= rhs.getLength();
	int totalLength	= myLength + otherLength;
	String::Rep* r	= String::Rep::Create(totalLength);
	char* p			= r->getData();
	String::MemCpy(p,lhs.m_string,myLength);
	String::MemCpy(p+myLength,rhs.m_string,otherLength+1);
	return String(r);
}

//------------------------------------------------------------------------
String
operator+(const String& lhs, const char* rhs)
{
	int myLength	= lhs.getLength();
	int otherLength	= String::StrLen(rhs);
	int totalLength	= myLength + otherLength;
	String::Rep* r	= String::Rep::Create(totalLength);
	char* p			= r->getData();
	String::MemCpy(p,lhs.m_string,myLength);
	String::MemCpy(p+myLength,rhs,otherLength+1);
	return String(r);
}

//------------------------------------------------------------------------
String
String::substr(dbbindex index, dbbindex maxChars) const
{
	if(maxChars-index>getLength())
	{
		maxChars = getLength() - index;
	}
	Rep* r	= Rep::Create(maxChars);
	char* p	= r->getData();
	MemCpy(p,m_string+index,maxChars);
	p[maxChars] = '\0';
	return String(r);
}

//------------------------------------------------------------------------
String
String::asUpperCase() const
{
	Rep* r	= _getRep().clone();
	char* p = r->getData();
	for(dbbsize i=0;i<getLength();++i)
	{
		p[i] = ToUpper(p[i]);
	}
	return String(r);
}

//------------------------------------------------------------------------
String
String::asLowerCase() const
{
	Rep* r	= _getRep().clone();
	char* p	= r->getData();
	for(dbbsize i=0;i<getLength();++i)
	{
		p[i] = ToLower(p[i]);
	}
	return String(r);
}

//------------------------------------------------------------------------
bool
String::beginsWith(const String &s) const
{
	return this->beginsWith(s.cString());
}

//------------------------------------------------------------------------
bool
String::beginsWith(const char *s) const
{
	for(dbbsize i=0;s[i]!=0;++i)
	{
		if(i>=getLength()||m_string[i]!=s[i])
		{
			return false;
		}
	}
	return true;
}

//------------------------------------------------------------------------
bool
String::endsWith(const String& s) const
{
	if(getLength()<s.getLength())
	{
		return false;
	}
	dbbsize offset	= getLength() - s.getLength();
	for(dbbsize i=0;i<s.getLength();++i)
	{
		if(m_string[i+offset]!=s[i])
		{
			return false;
		}
	}
	return true;
}

//------------------------------------------------------------------------
bool
String::endsWith(const char* s) const
{
	dbbsize offset	= getLength() - StrLen(s);
	if(offset<0)
	{
		return false;
	}
	for(dbbsize i=0;s[i]!=0;++i)
	{
		if(m_string[i+offset]!=s[i])
		{
			return false;
		}
	}
	return true;
}

//------------------------------------------------------------------------
String
String::replace(char oldchar, char newchar) const
{
	Rep* r	= _getRep().clone();
	char* p	= r->getData();
	for(dbbsize i=0;i<getLength();++i)
	{
		if(p[i]==oldchar)
		{
			p[i] = newchar;
			break;
		}
	}
	return String(r);
}

//------------------------------------------------------------------------
String
String::replaceAll(char oldchar, char newchar) const
{
	Rep* r	= _getRep().clone();
	char* p	= r->getData();
	for(dbbsize i=0;i<getLength();++i)
	{
		if(p[i]==oldchar)
		{
			p[i] = newchar;
		}
	}
	return String(r);
}

//------------------------------------------------------------------------
String
String::replace(const String& from, const String& to) const
{
	String ret;
	dbbsize current	= 0;
	const char* thisp = this->cString();
	const char* fromp = from.cString();
	const char* p = StrStr(thisp, fromp);
	while(p)
	{
		dbbsize i	= static_cast<int>( p - thisp - current ); //XX 64 bit issue
		ret			+= this->substr(current, i ); 
		ret			+= to;
		current		+= i + from.getLength();
		break;
		p			= StrStr(thisp + current, fromp);
	}
	if( current != 0 )
	{
		ret			+= (thisp + current);
		return ret;
	}
	return *this;
}

//------------------------------------------------------------------------
String
String::replaceAll(const String& from, const String& to) const
{
	String ret;
	dbbsize current	= 0;
	const char* thisp = this->cString();
	const char* fromp = from.cString();
	const char* p = StrStr(thisp, fromp);
	while(p)
	{
		dbbsize i	= static_cast<int>( p - thisp - current ); //XX 64 bit issue
		ret			+= this->substr(current, i ); 
		ret			+= to;
		current		+= i + from.getLength();
		p			= StrStr(thisp + current, fromp);
	}
	if( current != 0 )
	{
		ret			+= (thisp + current);
		return ret;
	}
	return *this;
}